/*
 * IMFloodingM.nc -- flooding component implementation
 *
 * Copyright (C) Andrey Vihrov <andrey.vihrov@gmail.com>, 2010
 * Copyright (C) Askar Salimbaev <ghostinshell@gmail.com>, 2010
 *
 * This file is part of Intermote.
 *
 * Intermote is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Intermote is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Intermote.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <AM.h>

#include <Intermote.h>

module IMFloodingM {

	provides {
		interface Init;

		interface Packet;
		interface IMFloodingAddress as FloodingAddress;

		interface Send[im_flood_id_t];
		interface Intercept[im_flood_id_t];
	}

	uses {
		interface Packet as ProtocolPacket;
		interface AMPacket as ProtocolAMPacket;
		interface AMSend as ProtocolSend;
		interface Receive as ProtocolReceive;
	}

}
implementation {

	/**
	 * Flooding data packet
	 */
	typedef nx_struct {
		nx_struct fl_headers {
			nx_uint8_t   id;
			nx_uint8_t   ttl;
			nx_uint16_t  source; /* Source address */
		} headers;

		nx_uint8_t  data[];
	} FloodingPacket;
	#define FL_HDR_SIZE sizeof(nx_struct fl_headers)

	/* Buffer for resending messages */
	/* TODO Maybe replace this with a queue */
	message_t resend_msg;
	bool resend_busy = FALSE;


	/* Init interface operations */

	command error_t Init.init (void)
	{
		call ProtocolPacket.clear(&resend_msg);

		return SUCCESS;
	}


	/* Packet interface operations */

	command void Packet.clear (message_t *msg)
	{
		call ProtocolPacket.clear(msg);
	}

	command uint8_t Packet.payloadLength (message_t *msg)
	{
		return call ProtocolPacket.payloadLength(msg) - FL_HDR_SIZE;
	}

	command void Packet.setPayloadLength (message_t *msg, uint8_t len)
	{
		call ProtocolPacket.setPayloadLength(msg, len + FL_HDR_SIZE);
	}

	command uint8_t Packet.maxPayloadLength (void)
	{
		return call ProtocolPacket.maxPayloadLength() - FL_HDR_SIZE;
	}

	command void *Packet.getPayload (message_t *msg, uint8_t len)
	{
		FloodingPacket *pack = call ProtocolPacket.getPayload(msg, len + FL_HDR_SIZE);

		if (pack)
			return pack->data;
		else
			return NULL;
	}


	/* IMFloodingAddress interface operations */

	command am_addr_t FloodingAddress.address (message_t *msg)
	{
		const FloodingPacket *pack
			= call ProtocolPacket.getPayload(msg, call ProtocolPacket.payloadLength(msg));

		/* msg should be a valid IMFloodingP packet, so we may skip the check */
		return pack->headers.source;
	}


	/* Send interface operations */

	command error_t Send.send[im_flood_id_t id] (message_t *msg, uint8_t len)
	{
		FloodingPacket *pack = call ProtocolPacket.getPayload(msg, len + FL_HDR_SIZE);

		if (!pack)
			return FAIL;

		pack->headers.id     = id;
		pack->headers.ttl    = FL_MAX_HOPS;
		pack->headers.source = call ProtocolAMPacket.address();

		return call ProtocolSend.send(AM_BROADCAST_ADDR, msg, len + FL_HDR_SIZE);
	}

	command error_t Send.cancel[im_flood_id_t id] (message_t *msg)
	{
		return call ProtocolSend.cancel(msg);
	}

	command uint8_t Send.maxPayloadLength[im_flood_id_t id] (void)
	{
		return call Packet.maxPayloadLength();
	}

	command void *Send.getPayload[im_flood_id_t id] (message_t *msg, uint8_t len)
	{
		return call Packet.getPayload(msg, len);
	}

	default event void Send.sendDone[im_flood_id_t id] (message_t *msg, error_t err)
	{

	}


	/* ProtocolSend interface events */

	event void ProtocolSend.sendDone (message_t *msg, error_t err)
	{
		const FloodingPacket *pack
			= call ProtocolPacket.getPayload(msg, call ProtocolPacket.payloadLength(msg));

		if (msg != &resend_msg)
			signal Send.sendDone[pack->headers.id](msg, err);
		else
			resend_busy = FALSE;
	}


	/* Intercept interface events */

	default event bool Intercept.forward[im_flood_id_t id] (message_t *msg, void *payload, uint8_t len)
	{
		return TRUE;
	}


	task void resend_task (void);


	/* ProtocolReceive interface events */

	event message_t *ProtocolReceive.receive (message_t *msg, void *payload, uint8_t len)
	{
		FloodingPacket *pack = payload;

		if (len < FL_HDR_SIZE)
			return msg;

		if (pack->headers.source == call ProtocolAMPacket.address())
			return msg;

		if (!resend_busy)
		{
			if (signal Intercept.forward[pack->headers.id](msg, pack->data, len - FL_HDR_SIZE))
			{
				FloodingPacket *newpack
					= call ProtocolPacket.getPayload(&resend_msg, len);

				memcpy(newpack, pack, len);
				post resend_task();
				resend_busy = TRUE;
			}
		}

		return msg;
	}


	/**
	 * Broadcast the packet
	 */
	task void resend_task (void)
	{
		size_t len = call ProtocolPacket.payloadLength(&resend_msg);
		FloodingPacket *pack = call ProtocolPacket.getPayload(&resend_msg, len);

		if (--pack->headers.ttl == 0)
		{
			resend_busy = FALSE;
			return;
		}

		if (call ProtocolSend.send(AM_BROADCAST_ADDR, &resend_msg, len) != SUCCESS)
			resend_busy = FALSE;
	}

}
